Moving To Windows Part 0

Pheon

As the world becomes engulfed with Windows, in all its various forms it appears only now that it is slowly becoming used in demos at large. It is my hope that coders can get off their arse and out of the deep dark dank of DOS and move forward and once again take technology to new heights. This tute is all the need-to-know stuff skipping over many if not almost all the details of Win32 coding. I'll refer to Windows (TM), the operating system, as Win32 so not to confuse it with a Win32 Window.

Comments - WTF are they doing here

Just a note about my programming style: I use heaps of comments and use a very simple Hungarian notation for variable names.

g_ - means it's a Global variable
m_ - means it's a Static Module variable

Whether you're working by solo or in a team, it's my opinion that what the code does is just as important as how it looks, which I hope you will see.

What's in hell is this all about?

This is only the first tute in a series that I hope gives you a firm base to get stuck straight into Win32 coding and basically skip all the bullshit.

Tools

By far the most important thing for any coder is a compiler, and by far the best for Windows is Visual C++. It has a nice IDE somewhat reminiscent of BC3.1 but much more powerful. There are other options, mostly Watcom, Borland and DJGPP, but these seem to lack the full integration of Win32. I suggest Visual C++ version 5.0 or above. The down side is it only creates Win32 executables, takes masses of RAM and HDD space and has its various quirks, welcome to the land of Windows.

DirectX should be used but not required, I believe VC (Visual C) 5.0 has the DirectX 3 SDK on it and VC 6.0 has DX 5 SDK, otherwise it's available on the Microsoft site http://www.microsoft.com/DirectX. Depending on when was the last release of DirectX you may be able to download it, or you can order it on CD. A version of Windows9x is also useful.

The Skeleton

Windows does things quite differently from DOS. Here's a basic list of differences:

- main() is now WinMain().

- You must 'give up' your CPU time.

- Most of the interface with 'outside world' is done using a message callback usually named WindProc().

- You must create some windows.

In DOS main() is the entry point to your program when you type program.exe at the prompt, in Win32 surpassingly enough it's called this WinMain(). It's used in exactly the same way with the exception of the calling parameters.

The prototype goes like this:

    int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                       LPTSTR lpCmdLine, int nCmdShow);

Many people use command line options. With main() you have the argc/argv. Instead we use lpCmdLine, which is similar to argv except it's just one big string you need to parse. An example might be to see if the '-nogafferivegotaheadache' switch was specified at the prompt:

    if (strstr( lpCmdLine, "-nogafferivegotaheadache") == 0) m_Arse = 0;

You must also save the hInstance of the program, this is required for many Win32 functions.

Giving up the CPU

As Windows is a preemptive multitasking environment you need to let other 'things' do shit. This is done by having a message loop.

A simple demo Message loop will look like this:

    do
    {
        // Windows shit
        if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        // Demo stuff
        DemoLoop();

    } while (msg.message != WM_QUIT);

You may see code that uses GetMessage() instead of PeekMessage(). The difference is, GetMessage() will only return if it has a message to processes, while PeekMessages() returns if there is no Message. This allows us a huge slice of the CPU time while still being responsive to Win32 Messages. If you think of keyboard I/O loop, e.g.

    if (kbhit())
    {
        getch();
    }

and replace kbhit() with GetMessage(); and getch() with TranslateMessage() and DispatchMessage() then you should get the idea behind PeekMessages().

These TranslateMessage() and DispatchMessage() functions just relay the messages to your window to your message handler WinProc(), it's very standard and you shouldn't think about it too hard.

The while() statement at the end is our exit from program condition. The Win32 WM_QUIT message occurs when the application is being destroyed, think of it like this DOS like loop.

    do
    {
        // do our demo stuff
        DemoLoop();

        // get any keyboard input
        Keyboard_Update();

        // see if we are quitting
        if (Keyboard_GetKey( KEY_ESCAPE )) // will return TRUE if the
                                           // specified key is pressed
        {
            m_IWantToQuitThisProgram = 1;
        }

    } while (m_IWantToQuitThisProgram != 1);

So our "msg.message != WM_QUIT" is just like the m_IWantToQuitThisProgram flag.

In general this Win32 message loop gives your DemoLoop() an extremly high number of calls per second, this is good and what you would expect in any do/while loop. The added advantage is it also allows core Windows functions to operate so we can use its basic I/O functionality.

Message Mullets

To do anything useful with the user, like quitting or selecting resolutions, you need to deal with the main message loop for your window. This is usually a fairly simple but usually long procedure that just acts like an interface. It essential replaces the getch(); the kbhit(); in 43h, al; and int 15h all into a fairly constant interface.

A simple example:

    long CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
        switch(msg)
        {

            case WM_DESTROY:
                PostQuitMessage(0);
                break;

            case WM_KEYDOWN:
                switch(wParam)
                {
                    case VK_ESCAPE:
                        PostQuitMessage(0);
                        break;
                 }
                 break;

            case WM_KEYUP:
                 break;

        }

        return DefWindowProc(hWnd, msg, wParam, lParam);
     }

The WM_KEYDOWN message just tells you when keys are pressed, and WM_KEYUP when they are released. The interesting thing here is WM_DESTROY message, this is called when the user closes the window via Alt-F4, the 'X' button or any other method. It calls PostQuitMessage(), which sends the WM_QUIT message to our application. Now remember in the preceding section the

     while (msg.message != WM_QUIT)

condition for our main loop, and also see PostQuitMessage() in the WM_DESTROY and VK_ESCAPE cases. You can see that PostQuitMessage() is a function which does

     m_IWantToQuitThisProgram = 1

for our DOS based demo loop above. When the VK_ESCAPE key is pressed PostQuitMessage() is called and the program terminates, which you expect when pressing the 'ESC' key.

The DefWindowProc() just passes messages we receive to anyone else who is interested, which we won't go into detail with. Generally a demo just requires an escape key to skip or exit, and perhaps an up, down, left, right, enter keys to move though the configuration, all of this just requires you to put some more case statements in the WM_KEYDOWN switch (we'll get onto a more intuitive dialog based config setup later on).

Note: The key 'A' or 'a' is denoted VK_A, 'B' 'b' as VK_B etc. It's not case sensitive as there is a 'Shift' VK_SHIFT key value.

Living with the Natives, Window creation

In general a demo will want to create one huge window that covers the entire screen and wipe out any resemblance of the Windows desktop, and then create a DirectDraw object set the screen resolution and get direct access to the frame buffer. While this will be covered later it's usually a good idea to create a smallish plain window and put some nice piccy up while decompressing data, or for selecting configuration options among other things. To create a Win32 Window you need to create a Win32 'class' here's a very simple example:

    WNDCLASS wc;

    memset( &wc, 0, sizeof(wc) );
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = g_hInstance;
    wc.hIcon = NULL;
    wc.hCursor = NULL;
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = m_AppName;

One very common practice is to zero out all Win32 structures, and then in the case of DirectX fill in a dwSize member of the structure with the size in bytes of the structure. Get used to it. There are just far too many Window(wc.style) styles to be listed here. I'm not even going to list any more, but what wc.style does is give general characteristics of the class. What we have set says to redraw the window whenever there is horizontal or vertical adjustment of the window. So if we move the window around the GUI with the mouse it will be updated. The WndProc is the Message handling procedure we described above, hInstance is the value we get from WinMain() (see above), and most importantly lpszClassName gives a text name to our class. This is very important as it's the only link between the Window and the Class.

Once this is filled out you register it with Win32:

    if (RegisterClass( &wc ))
    {

And if it's successful then create a window.

        hWnd = CreateWindow( m_AppName,
                             m_AppTitle,
                             WS_OVERLAPPEDWINDOW,
                             PosX, PosY,
                             Width, Height,
                             NULL,
                             NULL,
                             g_hInstance,
                             NULL);

What were doing is creating a window using the class called m_AppName we defined above, remember this must be the same as the class name set as wc.lpszClassName above. It creates a window with a Title of m_AppTitle with its Top left-hand corner of (PosX, PosY) and with Width and Height of (MyWidth, MyHeight), and using this g_hInstance (saved in Winmain()). The rest of the fields are of less relevance and you can check them out from your Win32 help files.

We now need to check if the window is created:

        if (hWnd != NULL)
        {
            ShowWindow(hWnd, SW_SHOW);

Now show it to the world, This the final leg of the marathon to display the window on the screen. There are various other parameters than SW_SHOW to do things like minimize, maximize but we just want to show it at the size specified in our CreateWindow() function above. Check the Win32 Help docs for more info.

        }
        else
        {
             MessageBox( NULL, "Unable to Create Window", "Error", MB_OK );
        }
    }
    else
    {
        MessageBox( NULL, "Unable to Register class", "Error", MB_OK );
    }

The MessageBox function is just a quick and easy way to say if something's wrong. Usually the NULL is the hWnd of the application but seeing as we don't have one we use the Desktop Window instead, which is specified as NULL.

Putting it all Together

Here's a simple code snippet to get your first Win32 program up and running, a VC 5.0 project.

   /**********************************************************************
   *
   *       Title:  WinCode0
   *       Desc:   A very simple Win32 Application
   *
   *       Note:
   *
   ***********************************************************************/

   #include <Windows.h>
   #include <Stdio.h>

   /**********************************************************************/
   // Defines
   /**********************************************************************/

   /**********************************************************************/
   // Globals
   /**********************************************************************/

   HWND                    g_hWnd;
   HINSTANCE               g_hInstance;

   /**********************************************************************/
   // Module Data
   /**********************************************************************/

   // window placement
   static int      m_WindowWidth;
   static int      m_WindowHeight;
   static int      m_WindowPosX;
   static int      m_WindowPosY;

   // class description
   static char *m_AppName  = "WinCode0";
   static char *m_AppTitle = "WinCode0 Title";

   /**********************************************************************/
   // Functions
   /**********************************************************************/

   // windowing functions
   static void Window_Destroy( void );
   static HWND Window_Create( int PosX, int PosY, int Width, int Height);

   /**********************************************************************/
   *
   *       Function:  WinMain();
   *
   *       Desc:      Execution entry point
   *
   *       Notes:  1) Think of this as main()
   *
   ***********************************************************************/
   int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR
   lpCmdLine, int nCmdShow)
   {
           MSG msg;

           // save our instance
           g_hInstance = hInstance;

           // set the dimentions of our windows. these are the sizes on the
           // desktop and in pixels
           m_WindowPosX = 0;
           m_WindowPosY = 0;
           m_WindowWidth = 320;
           m_WindowHeight = 240;

           // create a window
           g_hWnd = Window_Create( m_WindowPosX, m_WindowPosY,
                                   m_WindowWidth, m_WindowHeight );

           // did we successed
           if (g_hWnd != NULL)
           {

                   // the main message loop
                   do
                   {
                           // Windows shit
                           if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
                           {
                                   TranslateMessage(&msg);
                                   DispatchMessage(&msg);
                           }
                           // time to exit?
                           if( Keyboard->GetKeyStatus( DE5_KEY_ESCAPE) ==
                                           DE5_KEYBOARD_DOWN)
                           {
                                   PostQuitMessage( 0 );
                           }

                           Driver->BufferClear();
                           Driver->BufferSwap();

                   } while(msg.message!=WM_QUIT);

                   // the application is exiting

                   // close the window
                   Window_Destroy();
           }

           // return value of our program
           return 0;
   }

   /**********************************************************************/
   *
   *       Function:  WndProc()
   *
   *       Desc:      Our windows message loop
   *
   *       Notes:  1) This is message loop. we'll only process keyboard
   *                  input here
   *
   ***********************************************************************/
   static long CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM
   lParam)
   {

           // work out which message this is
           switch(msg)
           {
                   // this is called when DestroyWindow() is called or when
                   // the User Closes the window
                   case WM_DESTROY:
                           PostQuitMessage(0);
                           break;

                   // called when the user resizes the windows dimentions
                   case WM_SIZE:
                           break;

                   // called when a key is pressed
                   case WM_KEYDOWN:

                           // which key was it
                           switch( wParam )
                           {
                                   // the Esc key
                                   case VK_ESCAPE:
                                           // we want to quit the application
                                           PostQuitMessage(0);
                                           break;
                           }
                           break;

                   // called when the key is released
                   case WM_KEYUP:

                           switch( wParam )
                           {
                           }
                           break;
           }

           // let whoever else is interested in our messages process them
           return DefWindowProc(hWnd, msg, wParam, lParam);
   }

   /**********************************************************************/
   *
   *       Function:  Window_Create()
   *
   *       Desc:      Creates a standard window at the position and
   *                  dimentions specified
   *
   *       Notes:  1) Will return NULL if window creating failed
   *
   ***********************************************************************/
   static HWND Window_Create( int PosX, int PosY, int Width, int Height)
   {
           WNDCLASS        wc;
           HWND            hWnd;

           // assume we fail
           hWnd = NULL;

           // create the Window Class for our window
           memset( &wc, 0, sizeof(wc) );
           wc.style = CS_HREDRAW | CS_VREDRAW;
           wc.lpfnWndProc = WndProc;
           wc.cbClsExtra = 0;
           wc.cbWndExtra = 0;
           wc.hInstance = g_hInstance;
           wc.hIcon = NULL;
           wc.hCursor = NULL;
           wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
           wc.lpszMenuName = NULL;
           wc.lpszClassName = m_AppName;

           // attempt to create it
           if(RegisterClass( &wc ))
           {
                   // class has been created now create a window with it
                   hWnd = CreateWindow(    m_AppName,
                                           m_AppTitle,
                                           WS_OVERLAPPEDWINDOW,
                                           PosX, PosY,
                                           Width, Height,
                                           NULL,
                                           NULL,
                                           g_hInstance,
                                           NULL);

                   // did we succed in creating the window
                   if (hWnd)
                   {
                           // show it to the world
                           ShowWindow(hWnd, SW_SHOW);
                           UpdateWindow(hWnd);
                   }
           }

           return hWnd;
   }

   /**********************************************************************/
   *
   *       Function:  Window_Destroy()
   *
   *       Desc:      Closes and cleans up our window
   *
   *       Notes:  1) this dosent do anything as we assume this is being
   *                  called after the WM_QUIT message has been passed and
   *                  the window has already been destroyed.
   *
   ***********************************************************************/
   static void Window_Destroy( void )
   {
           /*
           I've put this here so we can abstract Window Creating/Destroying
           above the plain Win32 API. What we will do in the next tute is
           build on this frame work and extend this Window structure to GDI
           and DDraw
           */
   }

Conclusion

Hopefully we've covered the absolute bare and basics needed to get into Win32 coding. This application isn't very useful but we'll build functionality into this in the next few tutes. And yes I do comment and waste lots of space in source files with largely useless information... It's just the way I code, and I like it. Next time we'll create a basic demo framework and start to get something that looks useful to all you demo dudes out there.

Pheon/Aaron Foo . pheons@hotmail.com